require 'ann/neuron'
require 'ann/input_neuron'

module Ann
  class Network
    attr_reader :layers
    def initialize(*desc)
      work = desc.clone
      
      prev_layer = []
      work.shift.times do
        prev_layer << InputNeuron.new
      end
      @layers = [prev_layer]
      
      # Create the layers
      work.each do |l|
        layer = []
        l.times do
          layer << Neuron.new(prev_layer)
        end
        @layers.push layer
        prev_layer = layer
      end
    end
    
    def output
      work = layers.last
      if work.size == 1
        work.last.output
      elsif work.size > 1
        work.inject([]) {|result, n| result << n.output}
      end
    end
    
    def train(data, epochs, step, goal)
      1.upto(epochs) do |i|
        err = train_epoch(data, goal)
        return err if err <= goal
        yield(i, err) if i.modulo(step) == 0
      end
    end
    
    def train_epoch(data, goal)
      err = 0
      data.each do |d|
        self.load_input(d.first)
        
        total = 0
        layers.last.each_index do |j|
          total += (d.last[j] - layers.last[j].output)**2
        end
        avg = total / layers.last.size
        err = Math.sqrt(avg)
        
        layers.last.each do |n|
          n.adjust(err)
        end
        
        self.clear
      end
      err
    end
    
    def load_input(input)
      input.each_index do |i|
        self.layers.first[i].value = input[i]
      end
    end
    
    def clear
      layers.last.each do |n|
        n.clear
      end
    end
    
    def ==(o)
      @layers == o.instance_variable_get('@layers')
    end
  end
end
